#ifndef PRYN_STREAM_H
#define PRYN_STREAM_H

#include <pryn/platform.h>

typedef struct PrynStream PrynStream;
typedef uint64_t PrynStreamPosition; /// A byte offset within a stream.
typedef int64_t PrynStreamOffset; /// An offset (positive or negative) from a position within a stream.

#include <pryn/string.h>

/** @defgroup PrynStream PrynStream C API
@{ */

PrynCallbackTypedef (PrynResult, PrynStreamReadFunction, (PrynStream *stream, void *output, size_t length, size_t *read, void *data))
	/**< @brief Read a number of bytes from the stream.

	@param stream The stream to read from.
	@param[out] output Written with the data read from the stream; must be at least @e length bytes of valid data.
	@param length The number of bytes to attempt to read.
	@param[out] read Written with the number of bytes actually written.
	@param data Parameter passed when registering the callback.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynCallbackTypedef (PrynResult, PrynStreamWriteFunction, (PrynStream *stream, const void *input, size_t length, size_t *written, void *data))
	/**< @brief Write a number of bytes to the stream.

	@param stream The stream to write to.
	@param[in] input The data to write to the stream; must be at least @e length bytes of valid data.
	@param length The number of bytes to attempt to write.
	@param[out] written Written with the number of bytes actually written from data.
	@param data Parameter passed when registering the callback.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynCallbackTypedef (PrynResult, PrynStreamSeekFunction, (PrynStream *stream, PrynStreamPosition position, void *data))
	/**< @brief Seek to a specific position within the stream.

	@param stream The stream to seek within.
	@param position The position to seek to.
	@param data Parameter passed when registering the callback.
	@returns #PrynResultSuccess or an error code; see #PrynResult. It's undefined what happens after an unsuccessful seek, but the tell function should return a valid value afterwards. */

PrynCallbackTypedef (PrynResult, PrynStreamTellFunction, (PrynStream *stream, PrynStreamPosition *position, void *data))
	/**< @brief Retrieve the current position within the stream from the start in bytes.

	@param stream The stream to retrieve the position of.
	@param[out] position Written with the stream position.
	@param data Parameter passed when registering the callback.
	@returns #PrynResultSuccess or an error code; see #PrynResult. Erroneous results shouldn't lead to an undefined state. */

PrynCallbackTypedef (PrynResult, PrynStreamCloseFunction, (PrynStream *stream, void *data))
	/**< @brief Close the stream, deleting all data.

	It is not the callback's responsibility to free any of the data pointers for callback use.

	@param stream The stream to close.
	@param data Parameter passed when registering the callback.
	@returns #PrynResultSuccess or an error code; see #PrynResult. This result is ignored. */

PrynImport (PrynResult, prynStreamCreate, (PrynStream **stream, PrynState *state)) ///< Create a new stream object. All the callbacks are set to defaults that return #PrynResultUnimplemented.

PrynImport (PrynResult, prynStreamOpenFile, (PrynStream **stream, PrynState *state, const PrynString *filename, bool read, bool write)) ///< Open a file as a stream. If only @e read is set, the file must exist. If only @e write is set, the file will be truncated to 0 bytes if it exists or created if it doesn't. If both are set, the file must exist and can be both modified and read from.

PrynImport (PrynResult, prynStreamClose, (PrynStream *stream)) ///< Close the stream, if it isn't already closed. This also frees the stream pointer, so any use past this function call will result in crashes and corruption.

PrynImport (PrynResult, prynStreamRead, (PrynStream *stream, void *data, size_t length, size_t *read))
	/**< @brief Read a number of bytes from the stream.

	@param stream The stream to read from.
	@param[out] data Written with the data read from the stream; must be at least @e length bytes of valid writable data. If not, the program will crash, with no attempt made (or seriously possible) to prevent that from happening.
	@param length The number of bytes to attempt to read.
	@param[out] read If non-null, written with the number of bytes read from the stream, or 0 on failure.
	@returns #PrynResultSuccess or an error code; see #PrynResult. Reading less than the requested number of bytes returns the success condition #PrynResultStreamNotAllRead. Use #prynStreamReadExact to return the error code #PrynResultStreamReadFailure on that condition. */

PrynImport (PrynResult, prynStreamReadExact, (PrynStream *stream, void *data, size_t length, size_t *read))
	/**< @brief Read a number of bytes from the stream.

	This transforms #prynStreamRead's result so that if it returns #PrynResultStreamNotAllRead, meaning it read fewer than @e length bytes, then this function returns #PrynResultStreamReadFailure instead. Otherwise the parameters, conditions, and result are the same. */

PrynImport (PrynResult, prynStreamWrite, (PrynStream *stream, const void *data, size_t length, size_t *written))
	/**< @brief Write a number of bytes to the stream.

	@param stream The stream to write to.
	@param[in] data The data to writ to the stream; must be at least @e length bytes of valid readable data. If not, the program will crash, with no attempt made (or seriously possible) to prevent that from happening.
	@param length The number of bytes to attempt to write.
	@param[out] written If non-null, written with the number of bytes written to the stream, or 0 on failure.
	@returns #PrynResultSuccess or an error code; see #PrynResult. Writing less than the requested number of bytes returns the success condition #PrynResultStreamNotAllWritten. Use #prynStreamWriteExact to return the error code #PrynResultStreamWriteFailure on that condition. */

PrynImport (PrynResult, prynStreamWriteExact, (PrynStream *stream, const void *data, size_t length, size_t *written))
	/**< @brief Write a number of bytes to the stream.

	This transforms #prynStreamWrite's result so that if it returns #PrynResultStreamNotAllWritten, meaning it wrote fewer than @e length bytes, then this function returns #PrynResultStreamWriteFailure instead. Otherwise the parameters, conditions, and result are the same. */

PrynImport (PrynResult, prynStreamWriteDynamicUInt, (PrynStream *stream, unsigned int value)) ///< Save an unsigned integer using a simple compression scheme.
PrynImport (PrynResult, prynStreamWriteDynamicString, (PrynStream *stream, const PrynString *value)) ///< Save a string as a dynamic unsigned integer length followed by the UTF-8 data.
PrynImport (PrynResult, prynStreamReadDynamicUInt, (PrynStream *stream, unsigned int *value)) ///< Load an unsigned integer using a simple compression scheme.
PrynImport (PrynResult, prynStreamReadDynamicString, (PrynStream *stream, PrynString *value)) ///< Load a string as a dynamic unsigned integer length followed by the UTF-8 data.

PrynImport (PrynResult, prynStreamState, (PrynStream *stream, PrynState **output)) ///< Retrieve the state the stream exists within.

#ifdef PrynInternalStructs
/** @brief An input, output, or input/output stream of data.
	@ingroup PrynStream
*/
struct PrynStream
{
#ifdef PrynInternal
	PrynCommonObject pCommonObject; ///< Common fields you'd expect in an object, like tags and monitors.
	PrynState *pState; ///< State the stream exists within.
	void *pProprietary; ///< Proprietary information relevant to the stream type.
	PrynCallback (PrynStreamReadFunction, pReadFunction); ///< Read a number of bytes from the stream.
	PrynCallback (PrynStreamWriteFunction, pWriteFunction); ///< Write a number of bytes to the stream.
	PrynCallback (PrynStreamSeekFunction, pSeekFunction); ///< Seek to a specific position within the stream.
	PrynCallback (PrynStreamTellFunction, pTellFunction); ///< Retrieve the current position within the stream.
	PrynCallback (PrynStreamCloseFunction, pCloseFunction); ///< Close the stream, deleting all data.
#endif /* PrynInternal */

#if __cplusplus
	static inline PrynStream *Create (PrynState *state) { PrynStream *output; state->checkError (prynStreamCreate (&output, state)); return output; } ///< Create a new stream object. All the callbacks are set to defaults that return #PrynResultUnimplemented.

	static inline PrynStream *OpenFile (PrynState *state, const PrynString &filename, bool read, bool write) { PrynStream *result; state->checkError (prynStreamOpenFile (&result, state, &filename, read, write)); return result; } ///< Open a file as a stream. If only @e read is set, the file must exist. If only @e write is set, the file will be truncated to 0 bytes if it exists or created if it doesn't. If both are set, the file must exist and can be both modified and read from.

	void close () { prynStreamClose (this); } ///< Close the stream, if it isn't already closed. As this frees the pointer, any attempt to use the stream past this point will cause corruption and crashes.

	size_t read (void *data, size_t length) { size_t read; checkError (prynStreamRead (this, data, length, &read)); return read; }
		/**< @brief Read a number of bytes from the stream.

		@param[out] data Written with the data read from the stream; must be at least @e length bytes of valid writable data. If not, the program will crash, with no attempt made (or seriously possible) to prevent that from happening.
		@param length The number of bytes to attempt to read.
		@returns The number of bytes read from the stream, which may be fewer than expected. Use #readExact to throw an exception on this condition. */

	void readExact (void *data, size_t length) { checkError (prynStreamReadExact (this, data, length, 0)); }
		/**< @brief Read a number of bytes from the stream, throwing an error if not all can be read.

		If it reads fewer than the expected number of bytes, a #PrynException of type #PrynResultStreamReadFailure is thrown. */

	size_t write (const void *data, size_t length) { size_t written; checkError (prynStreamWrite (this, data, length, &written)); return written; }
		/**< @brief Write a number of bytes to the stream.

		@param[in] data The data to writ to the stream; must be at least @e length bytes of valid readable data. If not, the program will crash, with no attempt made (or seriously possible) to prevent that from happening.
		@param length The number of bytes to attempt to write.
		@returns The number of bytes written to the stream, which may be fewer than expected. Use #writeExact to throw an exception on this condition. */

	void writeExact (const void *data, size_t length) { checkError (prynStreamWriteExact (this, data, length, 0)); }
		/**< @brief Write a number of bytes to the stream, or throw an exception on failure.

		If it writes fewer than the expected number of bytes, a #PrynException of type #PrynResultStreamWriteFailure is thrown. */

	unsigned int readDynamicUInt () { unsigned int output; checkError (prynStreamReadDynamicUInt (this, &output)); return output; } ///< Load an unsigned integer using a simple compression scheme.
	PrynString readDynamicString () { PrynString output; checkError (prynStreamReadDynamicString (this, &output)); return output; } ///< Load a string as a dynamic unsigned integer length followed by the UTF-8 data.

	void writeDynamicUInt (unsigned int value) { checkError (prynStreamWriteDynamicUInt (this, value)); } ///< Save an unsigned integer using a simple compression scheme.
	void writeDynamicString (const PrynString &value) { checkError (prynStreamWriteDynamicString (this, &value)); } ///< Save a string as a dynamic unsigned integer length followed by the UTF-8 data.

	PrynState *state () { PrynState *output; checkError (prynStreamState (this, &output)); return output; } ///< Retrieve the state this stream exists within.

	inline PrynResult checkError (PrynResult result) { return prynCheckErrorBase (state (), result); } ///< If the result is below zero, throw an error.

#endif /* __cplusplus */
};
#endif /* PrynInternalStructs */

/** @} */ // (group)

#endif /* PRYN_STREAM_H */
